#include "qparser.h"

QParser::QParser(QWidget *parent) :
  QWidget(parent)
{
  curstate=START;
  vecIndex=0;
  validList=false;
}

// Parsing routine v1.1
void QParser::dataparse(QByteArray inarray)
{
  quint16 i=0;
  byteDecision check = BYTE_HANDLED;
  quint16 curSize=0;
  // We will eliminate bytes when one of the following things happen:
  //  1. A full vector is found and emitted
  //  We will crop all bytes up to and including the current one
  //  Reset i
  //  2. The current byte makes the list invalid
  //  Crop the first byte in the array, and reset vecIndex
  //  Reset i
  //  3. The byte is handled properly
  //  Increment i

  if(validList)
  {
    curSize=inarray.size();
    while(curSize>0)
    {
      check = CheckByte(inarray[i]);
      switch(check)
      {
      case LIST_INVALID:
        inarray=inarray.right(inarray.size()-1);
        i=0;
        //        vecIndex=0;
        //        varList[0].arrayfound.clear();
        break;
      case LIST_COMPLETE:
        inarray=inarray.right(inarray.size()-i-1);
        i=0;
        //        vecIndex=0;
        //        varList[0].arrayfound.clear();
        break;
      case BYTE_HANDLED:
        i++;
        if(i==curSize)
          inarray.clear();
        break;
      default:
        break;
      }
      curSize=inarray.size();
    }
  }
}


// CheckByte receives a single byte, and allocates it to the corresponding variable.
// There are two special cases of byte processing:

// 1. The current variable is a variable-length byte array and a number is received.
// If the number can be allocated to the next variable (or variable #0 if the current
// byte array is the last element in the list), the current byte array is closed.
// Otherwise the function returns INVALID_MATCH

// 2. The current variable is a variable-length number and a non-valid byte is received.
// If the byte can be allocated to the next variable (or variable #0 if the current number
// is the last element in the list), the current number is closed.

byteDecision QParser::CheckByte(char onebyte)
{
  QByteArray checkarray = varList[vecIndex].arrayfound;
  QRegExp regexp;
  byteDecision dec;
  // Look for an array of bytes that can contain +/-, spaces, and decimal points.
  //  regexp.setPattern(" *[-+]? *([0-9]+\\.?[0-9]*|\\.[0-9]+)");
  regexp.setPattern("( *[-+]? *\\d+\\.?\\d*)|( *[-+]? *\\d*\\.?\\d+)| +| *[+-]| *[+-] +| *[+-]? *\\.");
  checkarray.append(onebyte);

  // Check for Number/Byte
  switch(varList.at(vecIndex).type)
  {
  case BYTEARRAY:
    // Possible combinations:
    // Matched - accepts numbers
    // Fixed length - accepts numbers
    // Variable length - doesn't accept numbers
    if(varList.at(vecIndex).matched)
    {
      // Matched
      // Possibilities:
      // 1. Adding byte to the checkarray makes it valid, but lengths don't match
      // 2. Byte completes the checkarray entirely
      // 3. Adding byte to the checkarray makes it invalid
      if(varList.at(vecIndex).arraytomatch.indexOf(checkarray) != -1)
      {
        dec=BYTE_HANDLED;
        varList[vecIndex].arrayfound=checkarray;
        if(checkarray.size()==varList.at(vecIndex).arraytomatch.size())
        {
          // 1
          dec = nextVariable();

        }
        //        return BYTE_HANDLED;
      }
      else
      {
        // 3
        dec=LIST_INVALID;
        clearVarList();
        vecIndex=0;
        tryFirstVariable(onebyte);
        //        return LIST_INVALID;
      }
    }
    else if(varList.at(vecIndex).fixed)
    {
      // Fixed
      // Possibilities:
      // 1. Byte array matches the length prescribed
      // 2. Byte array length doesn't match
      // Fixed length
      dec=BYTE_HANDLED;
      varList[vecIndex].arrayfound=checkarray;
      if(checkarray.size()==varList.at(vecIndex).length)
      {
        // 1
        dec=nextVariable();
        //                return nextVariable();
      }
      //      return BYTE_HANDLED;
    }
    else
    {
      // Variable length
      // Possibilities:
      // 1. Byte array stays under the 128-byte limit, no numbers found
      // 2. Byte array reaches 128-byte limit
      // 3. Number
      if(checkarray.size()<128)
      {
        if(onebyte>=48 && onebyte<=57)
        {
          // 3
          // Is there another variable left?
          if(vecIndex<varList.size()-1)
          {
            if(tryNextVariable(onebyte))
            {
              dec=BYTE_HANDLED;
            }
            else
            {
              dec=LIST_INVALID;
              vecIndex=0;
              clearVarList();
              tryFirstVariable(onebyte);
            }
          }
          else
          {
            // Can we use the number in the starting variable?

            dec=LIST_COMPLETE;
            tryFirstVariable(onebyte);
          }
        }
        else
        {
          // 1
          dec=BYTE_HANDLED;
          varList[vecIndex].arrayfound=checkarray;
        }
      }
      else
      {
        // 2
        dec=LIST_INVALID;
        clearVarList();
        vecIndex=0;
      }
    }
    break;
  case NUMBER:
    // Is new number a valid addition?
    // A valid number will consist of the following:
    //       1        2       3        4       5       6      7
    //    [spaces] [+ / -] [spaces] [zeros] [number] [dot] [number]
    //    opt.      opt.    opt.      opt.    opt.    opt.   opt.

    if(regexp.indexIn(checkarray) == 0)
    {
      // Possible combinations:
      // 1. Fixed length
      // 2. Variable length

      // The regular expression will return 0 as long as the first portion of the array checks:
      // "255F"  will return a 0 but we need to handle the case where the matched length DNE
      // the checkarray size
      if(regexp.matchedLength()!=checkarray.size())
      {
        // The latest byte does not fit the regular expression
        // Two options:
        // 1. The current number has fixed length
        // 2. The current number has variable length
        if(varList[vecIndex].fixed)
        {
          dec=LIST_INVALID;
          vecIndex=0;
          clearVarList();
          tryFirstVariable(onebyte);
        }
        else
        {
          // Can we use the byte in the following variable?
          if(vecIndex<varList.size()-1)
          {
            dec=BYTE_HANDLED;
            if(tryNextVariable(onebyte))
            {
              dec=BYTE_HANDLED;
            }
            else
            {
              dec=LIST_INVALID;
              vecIndex=0;
              clearVarList();
              tryFirstVariable(onebyte);
            }
          }
          else
          {
            // Can we use the byte in the first variable?
            dec=LIST_COMPLETE;
            nextVariable();
            tryFirstVariable(onebyte);
          }
        }
      }
      else
      {
        // The regex match is still valid
        dec=BYTE_HANDLED;
        varList[vecIndex].arrayfound=checkarray;
        if(varList[vecIndex].fixed)
        {
          // Fixed length
          if(checkarray.size()==varList.at(vecIndex).length)
          {
            dec=nextVariable();
            //          return dec;
          }

        }
      }
      //      return BYTE_HANDLED;
    }
    else
    {
      // Found non-number character:
      // Possible combinations:
      // 3. Fixed length
      // 4. Variable length
      if(varList[vecIndex].fixed)
      {
        // 3
        // Can we use this byte as the first byte in the list?
        dec=LIST_INVALID;
        vecIndex=0;
        clearVarList();
        tryFirstVariable(onebyte);
      }
      else
      {
        // 4
        if(vecIndex<varList.size()-1)
        {
          if(tryNextVariable(onebyte))
          {
            dec=BYTE_HANDLED;
          }
          else
          {
            dec=LIST_INVALID;
            tryFirstVariable(onebyte);
          }
        }
        else
        {
          // Can we use the number in the starting variable?
          dec=LIST_COMPLETE;
          tryFirstVariable(onebyte);
        }
      }
    }
    break;
  default:
    break;
  }
  return dec;
}

byteDecision QParser::nextVariable()
{
  QByteArray cleanNumber;
  cleanNumber.clear();
  if(varList[vecIndex].type==NUMBER)
  {
    // Clean array found:
    // If there are any spaces in between the sign and the first digit/period, we need to remove them:
    for(quint8 i=0;i<varList.at(vecIndex).arrayfound.size();i++)
    {
      if(varList.at(vecIndex).arrayfound.at(i)!=' ')
      {
        cleanNumber.append(varList.at(vecIndex).arrayfound.at(i));
      }
    }
    varList[vecIndex].floatvalue=cleanNumber.toDouble();
//    varList[vecIndex].floatvalue=varList.at(vecIndex).arrayfound.toDouble();
  }
  vecIndex++;
  if(vecIndex==varList.size())
  {
    emit outputList(varList,true,QString("List OK"));
    vecIndex=0;
    clearVarList();
    return LIST_COMPLETE;
  }
  else
  {
    varList[vecIndex].arrayfound.clear();
    return BYTE_HANDLED;
  }
}

void QParser::clearVarList()
{
  for(quint16 i=0;i<varList.size();i++)
  {
    varList[i].arrayfound.clear();
  }
}

bool QParser::tryNextVariable(char newbyte)
{
  if(varList[vecIndex+1].type==BYTEARRAY)
  {
    // Try to fit the offending byte in a BYTEARRAY
    if(varList[vecIndex+1].matched)
    {
      if(varList[vecIndex+1].arraytomatch.at(0)==newbyte)
      {
        varList[vecIndex+1].arrayfound.append(newbyte);
        if(varList[vecIndex+1].arraytomatch.length()==1)
        {
          if((vecIndex+1)==varList.size()-1)
          {
            //            vecIndex++;
            nextVariable();
          }
          else
          {
            vecIndex++;
          }
        }
        return true;
      }
    }
    else
    {
      varList[vecIndex+1].arrayfound.append(newbyte);
      if(varList[vecIndex+1].fixed && varList[vecIndex+1].length==1)
      {
        if(vecIndex+1==varList.size()-1)
        {
          vecIndex++;
          nextVariable();
        }
        else
        {
          vecIndex+=2;
        }
      }
      return true;
    }
  }
  else
  {
    // Try to fit the offending byte in a NUMBER
    switch(newbyte)
    {
    case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
    case '-': case '+': case '.': case ' ':
      varList[vecIndex+1].arrayfound.append(newbyte);
      if(varList[vecIndex+1].fixed && varList[vecIndex+1].length==1)
      {
        if((vecIndex+1)==varList.size()-1)
        {
          nextVariable();
          //          vecIndex=0;
        }
        else
        {
          vecIndex++;
        }
      }
      return true;
      break;
    default:
      break;
    }
  }
  return false;
}

void QParser::tryFirstVariable(char newbyte)
{
  if(varList[0].type==BYTEARRAY)
  {
    // Try to fit the offending byte in a BYTEARRAY
    if(varList[0].matched)
    {
      if(varList[0].arraytomatch.at(0)==newbyte)
      {
        varList[0].arrayfound.append(newbyte);
        if(varList[0].arraytomatch.length()==1)
        {
          vecIndex=1;
        }
      }
    }
    else
    {
      varList[0].arrayfound.append(newbyte);
      if(varList[0].fixed && varList[0].length==1)
      {
        vecIndex=1;
      }
    }
  }
  else
  {
    // Try to fit the offending byte in a NUMBER
    switch(newbyte)
    {
    case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
    case '-': case '+': case '.': case ' ':
      varList[0].arrayfound.append(newbyte);
      if(varList[0].fixed && varList[0].length==1)
      {
        vecIndex=1;
      }
      break;
    default:
      break;
    }
  }
}

void QParser::addVariable(variable newvar,int index)
{
  varList.insert(index,newvar);
}

void QParser::remVariable(int index)
{
  varList.removeAt(index);

}

int QParser::varlistSize()
{
  return varList.size();
}

variable QParser::variableAt(int index)
{
  return varList[index];
}

void QParser::setvarProperty(int index,variableProperty prop,variable invar)
{
  switch(prop)
  {
  case VARIABLETYPE:
    varList[index].type=invar.type;
    break;
  case VARFIXED:
    varList[index].fixed=invar.fixed;
    break;
  case VARMATCHED:
    varList[index].matched=invar.matched;
    break;
  case VARLENGTH:
    varList[index].length=invar.length;
    break;
  case VARARRAY:
    varList[index].arraytomatch=invar.arraytomatch;
    break;
  case VARFULL:
    varList[index]=invar;
    break;
  }
}

void QParser::setType(int index, vartype newtype)
{
  varList[index].type=newtype;
}

void QParser::setMatched(int index, bool match)
{
  varList[index].matched=match;
}

void QParser::setFixed(int index, bool fix)
{
  varList[index].fixed=fix;
}

void QParser::setLength(int index, int len)
{
  varList[index].length=len;
}

void QParser::setArray(int index, QByteArray array)
{
  varList[index].arraytomatch=array;
}

void QParser::setName(int index, QString newname)
{
  varList[index].name=newname;
}

bool QParser::testList()
{

  testout.clear();
  if(varList.size()<2)
  {
    testout.append("Parser will only work with two or more variables");
    validList=false;
    return validList;
  }

  // Check that for every variable-length byte array there is a number after it, or a matched byte array
  for(int j=0;j<varList.size();j++)
  {
    if(varList.at(j).type==NUMBER)
    {
      varList[j].floatvalue=0;
    }
    if(varList.at(j).type==BYTEARRAY && !(varList.at(j).fixed || varList.at(j).matched))
    { // Variable length array, not matched
      if(j<varList.size()-1) // Still have variables left
      {
        if(!(varList.at(j+1).matched || varList.at(j+1).type==NUMBER))  // Next variable is not matched, and it's not a number either
        {
          testout.append("Use a number or matched-byte array in front of a variable-length byte array");
          validList=false;
          return validList;
        }
      }
      else // end of the vector
      {
        if(!(varList.at(0).matched || varList.at(0).type==NUMBER))
        {
          testout.append("Start with a number or matched byte array when the last variable is a variable-length byte array");
          validList=false;
          return validList;
        }
      }
    }
    if(varList.at(j).type==NUMBER && !(varList.at(j).fixed))  // do not put another number variable right after a variable-length number
    {
      if(j<varList.size()-1) // Still have variables left
      {
        if(varList.at(j+1).type!=BYTEARRAY) // next variable is a number
        {
          testout.append("Use a byte array in front of a variable-length number");
          validList=false;
          return validList;
        }
      }
      else
      {
        if(varList.at(0).type!=BYTEARRAY)  // no variables left, check first variable for byte type
        {
          testout.append("Start with a byte array if the last variable is a variable-length number");
          validList=false;
          return validList;
        }
      }

    }
    if(varList.at(j).type==BYTEARRAY && varList.at(j).matched)
    {
      // Do not accept matched byte if array to match is empty
      if(varList.at(j).arraytomatch.isEmpty())
      {
        testout.append("Array to match is empty");
        validList=false;
        return validList;
      }
    }
  }

  validList=true;
  testout.append("Variable list is valid");
  curstate=START;
  clearVarList();

  return validList;
}
